package com.acme.bankapp.domain;

import com.acme.bankapp.exception.BankException;
import com.acme.bankapp.exception.ClientExistsException;

import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class Bank implements Serializable{
    private Client[] clients = new Client[10];
    private int numberOfClients = 0;
    private ClientRegistrationListener[] listeners = new ClientRegistrationListener[10];

    public Bank() {
        // uncomment to enable listeners (I Don't want to delete this comment for understanding listeners)
        listeners[0] = new PrintClientListener();
        listeners[1] = new ClientRegistrationListener() {
            @Override
            public void onClientAdded(Client c) {
                System.out.print("Full information about new client: ");
                c.printClientInfo();
            }
        };
        listeners[2] = new EmailNotificationListener();
    }

    public Client[] getClients() {
        return Arrays.copyOf(clients, numberOfClients);
    }

    public void addClient(Client client) throws BankException {
        if (getClientByName(client.getName()) != null) {
            throw new ClientExistsException (client.getName());
        }
        if (numberOfClients == 10) {
            throw new BankException("Bank registry is full. 10 Clients at most.");
        }
        clients[numberOfClients] = client;
        numberOfClients++;
        for (ClientRegistrationListener listener : listeners) {
            if (listener != null) {
                listener.onClientAdded(client);
            }
        }
    }

    public Client getClientByName(String name) {
        for (Client client : getClients()) {
            if (client.getName().equals(name)) {
                return client;
            }
        }
        return null;
    }

    public void resetBank () {
        for (int i = 0; i < 10; i++) {
            clients[i] = null;
            numberOfClients = 0;
        }
    }

    private static class PrintClientListener implements ClientRegistrationListener {
        @Override
        public void onClientAdded(Client c) {
            System.out.println("New client has been added: " + c.getClientSalutation());
        }
    }

    private static class EmailNotificationListener implements ClientRegistrationListener {
        static EmailSender emailSender = new EmailSender();
        static {
            //TODO: find another way to stop the thread when everything is done
            emailSender.setDaemon(true);
            emailSender.start();
        }

        @Override
        public void onClientAdded(Client c) {
            emailSender.add(new Email("Hello", "Moscow", c));
        }
    }

    public static interface ClientRegistrationListener extends Serializable{
        void onClientAdded(Client c);
    }

    public static class Email {
        Client client;
        String emailAddress;
        String message;

        public Email(String message, String emailAddress, Client client) {
            this.message = message;
            this.emailAddress = emailAddress;
            this.client = client;
        }

        @Override
        public String toString() {
            return "Email{" +
                    "client=" + client.getName() +
                    ", emailAddress='" + emailAddress + '\'' +
                    ", message='" + message + '\'' +
                    '}';
        }
    }

    public static class EmailSender extends Thread {
        private List<Email> emails = new LinkedList<>();
        private Object waitMonitor = new Object();

        @Override
        public void run() {
            while (!isInterrupted()) {
                try {
                    processEmails();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void processEmails() throws InterruptedException {
            Email email = null;
            synchronized (emails) {
                if (!emails.isEmpty()) {
                    email = emails.remove(0);
                }
            }
            if (email != null) {
                send(email);
                return;
            }
            synchronized (waitMonitor){
                waitMonitor.wait();
            }
        }

        public void add(Email email) {
            synchronized (emails) {
                emails.add(email);
            }
            synchronized (waitMonitor) {
                waitMonitor.notify();
            }
        }

        private void send(Email email) {
            System.out.println(email);
        }
    }
}
